package listeners

import (
	"blockchains-aio/conf"
	"blockchains-aio/db"
	"blockchains-aio/stack"
	"blockchains-aio/util"
	"errors"
	"fmt"
	"github.com/gin-gonic/gin"
	"log"
	"net/http"
	"os"
	"strconv"
)

// stack2 is support docker to run in single machine(docker-compose)

func RegisterStack2(router *gin.Engine) {
	router.MaxMultipartMemory = 8 << 10 // 8 KB

	// assume to obtain stacks instance for all consensus.2024/11/29
	router.GET("/stack2/instance", doObtainStack2Instance)
	// bring up(restart) docker-composed containers.
	router.PUT("/stack2/instance", doRestartStack2Instance)
	// bring down docker-composed containers
	router.DELETE("/stack2/instance", doDeleteStack2Instance)

	//-----poa2 consensus as followed
	router.POST("/stack2/tmpl", doGenStack2Config)
	router.POST("/stack2", doDockStack2DeployForEth)

	//-----pow2 consensus as followed
	router.POST("/stack2/tmpl/pow", doGenStack2ConfigForPow)
	router.POST("/stack2/pow", doDockStack2DeployForPow)

	//-----bft2 consensus as followed
	router.POST("/stack2/tmpl/bft", doGenStack2ConfigForBft)
	router.POST("/stack2/bft", doDockStack2DeployForBft)

	//router.POST("/stack2/tmpl/pos", doGenStackConfigForPos)
	//router.POST("/stack2/poseth", doDockStackDeployForPosEth)

}

func doDeleteStack2Instance(ctx *gin.Context) {
	stackIdStr := ctx.Query("stackId")
	chainname := ctx.Query("chain")
	log.Printf("stackId: %s, chain: %s", stackIdStr, chainname)
	if stackIdStr == "" && chainname == "" {
		ctx.JSON(http.StatusBadRequest, NewErrorMessage(Stack, 1, "doDeleteStack2Instance", fmt.Sprintf("bad parameters: stackId or chainname")).Json())
		return
	}

	if stackIdStr != "" {
		stack := db.GetStackById(stackIdStr)
		if stack == nil {
			ctx.JSON(http.StatusBadRequest, NewErrorMessage(Stack, 2, "doDeleteStack2Instance", fmt.Sprintf("found no record: stackId")).Json())
			return
		}

		chainname = stack.ChainName
		log.Printf("find chainname in the db: %s", chainname)
	}

	chainFolder, _ := util.ExpandTilde(fmt.Sprintf("%s/%s", conf.GetNfsFolder(), chainname))
	theChainYaml := chainFolder + "/" + conf.DefaultComposeFileName
	log.Println("Path existed or not:", theChainYaml)
	if util.IsPathExists(theChainYaml) {
		//ctx.JSON(http.StatusBadRequest, NewErrorMessage(Stack, 3, "doDeleteStack2Instance", fmt.Sprintf("bad parameters: %s is unavailable", theChainYaml)).Json())
		//return
		var _, err = util.DockerComposeDown1(chainFolder)
		if err != nil {
			ctx.JSON(http.StatusInternalServerError, NewErrorMessage(Stack, 4, "doDeleteStack2Instance", fmt.Sprintf("Failed to bring down compose task: %v", err)).Json())
			return
		}

		err = os.RemoveAll(chainFolder)
		if err != nil {
			ctx.JSON(http.StatusInternalServerError, NewErrorMessage(Stack, 6, "doDeleteStack2Instance", fmt.Sprintf("Failed to remove chain folder: %v", err)).Json())
			return
		}

	}

	err := db.DelStackById(stackIdStr)
	if err != nil {
		ctx.JSON(http.StatusInternalServerError, NewErrorMessage(Stack, 5, "doDeleteStack2Instance", fmt.Sprintf("Failed to delete record: %v", err)).Json())
		return
	}

	ctx.JSON(http.StatusOK, "")
}

func doRestartStack2Instance(ctx *gin.Context) {
	chainname := ctx.Query("chainname")
	stackIdStr := ctx.Query("stackId")
	nodeNumStr := ctx.Query("nodeNum")
	if chainname == "" || stackIdStr == "" || nodeNumStr == "" {
		ctx.JSON(http.StatusBadRequest, NewErrorMessage(Stack, 1, "doRestartStack2Instance", fmt.Sprintf("bad parameter: chainname,stackId")).Json())
		return
	}
	nodeNum, err := strconv.Atoi(nodeNumStr)
	if err != nil {
		ctx.JSON(http.StatusBadRequest, NewErrorMessage(Stack, 2, "doRestartStack2Instance", fmt.Sprintf("bad parameter,not integer: nodeNum error: %v", err)).Json())
		return
	}

	stack := db.GetStackById(stackIdStr)
	if stack == nil {
		ctx.JSON(http.StatusBadRequest, NewErrorMessage(Stack, 3, "doRestartStack2Instance", fmt.Sprintf("found no stack by: stackId")).Json())
		return
	}
	stackId, _ := strconv.Atoi(stackIdStr)

	theNewChainYamlBytes, err := readFormFileBytes(ctx, "file")
	if err != nil {
		//ctx.JSON(http.StatusBadRequest, fmt.Sprintf("%v", err))
		ctx.JSON(http.StatusBadRequest, NewErrorMessage(Stack, 4, "doRestartStack2Instance", fmt.Sprintf("bad parameter: file")).Json())
		return
	}

	chainFolder, _ := util.ExpandTilde(fmt.Sprintf("%s/%s", conf.GetNfsFolder(), chainname))
	theChainYaml := chainFolder + "/" + conf.DefaultComposeFileName
	if err := os.WriteFile(theChainYaml, theNewChainYamlBytes, 0644); err != nil {
		//ctx.JSON(400, fmt.Sprintf("Failed to write file %s: %v", yaml, err))
		ctx.JSON(http.StatusBadRequest, NewErrorMessage(Stack, 5, "doRestartStack2Instance", fmt.Sprintf("Failed to write file %s: %v", theChainYaml, err)).Json())
		return
	}

	out, err := util.DockerComposeDeployUp3(chainFolder)
	if err != nil {
		//ctx.JSON(400, fmt.Sprintf("Failed to deploy compose task: %v", err))
		ctx.JSON(http.StatusBadRequest, NewErrorMessage(Stack, 6, "doRestartStack2Instance", fmt.Sprintf("Failed to deploy compose task: %v", err)).Json())
		return
	}

	err = db.UpdateStack(chainname, stack.Consensus, string(theNewChainYamlBytes), nodeNum, stackId)
	if err != nil {
		ctx.JSON(http.StatusBadRequest, NewErrorMessage(Stack, 7, "doRestartStack2Instance", fmt.Sprintf("UpdateStack database error: %v", err)).Json())
		return
	}

	ctx.JSON(http.StatusOK, out.String())
}

func doObtainStack2Instance(ctx *gin.Context) {
	chainname := ctx.Query("chainname")
	if chainname == "" {
		ctx.JSON(http.StatusBadRequest, NewErrorMessage(Stack, 1, "doObtainStack2Instance", fmt.Sprintf("bad query parameter: chainname")).Json())
		return
	}

	chainFolder, _ := util.ExpandTilde(fmt.Sprintf("%s/%s", conf.GetNfsFolder(), chainname))
	//theChainYaml := chainFolder + "/" + chainname + ".yml"
	theChainYaml := chainFolder + "/" + conf.DefaultComposeFileName

	isTheChainYamlExisted := util.IsPathExists(theChainYaml)
	if !isTheChainYamlExisted {
		ctx.JSON(http.StatusBadRequest, NewErrorMessage(Stack, 2, "doObtainStack2Instance", fmt.Sprintf("can not locate compose yaml file: %s", theChainYaml)).Json())
		return
	}

	theChainYamlBytes, err := os.ReadFile(theChainYaml)
	if err != nil {
		ctx.JSON(http.StatusInternalServerError, NewErrorMessage(Stack, 3, "doObtainStack2Instance", fmt.Sprintf("can not read compose yaml file: %s", theChainYaml)).Json())
		return
	}

	ctx.YAML(http.StatusOK, string(theChainYamlBytes))
}

func doDockStack2DeployForBft(ctx *gin.Context) {

	chainname := ctx.PostForm("chainname")
	//nodeNum := ctx.PostForm("nodeNum")
	nodeNum := 4
	consensus := ctx.PostForm("consensus")

	log.Printf("recieved {chainname:%s,consensus:%s}", chainname, consensus)

	if len(chainname) == 0 {
		//ctx.JSON(400, fmt.Sprintf("Failed to get chainname"))
		ctx.JSON(400, NewErrorMessage(Stack, 1, "doDockStack2DeployForBft", "Failed to get chainname").Json())
		return
	}

	if len(consensus) == 0 {
		consensus = "bft2"
	}

	file, _ := ctx.FormFile("file")
	f, err := file.Open()
	if err != nil {
		//ctx.JSON(400, fmt.Sprintf("Failed to open yaml:%v", err))
		ctx.JSON(400, NewErrorMessage(Stack, 2, "doDockStack2DeployForBft", fmt.Sprintf("Failed to open yaml:%v", err)).Json())
		return
	}
	defer f.Close()

	p := make([]byte, file.Size)
	if _, err := f.Read(p); err != nil {
		//ctx.JSON(400, fmt.Sprintf("Failed to read yaml: %v", err))
		ctx.JSON(400, NewErrorMessage(Stack, 3, "doDockStack2DeployForBft", fmt.Sprintf("Failed to read yaml: %v", err)).Json())
		return
	}

	if err := stack.CopyDirs_BFT2(chainname, consensus); err != nil {
		//ctx.JSON(400, fmt.Sprintf("Failed to copy necessary files: %v", err))
		ctx.JSON(400, NewErrorMessage(Stack, 4, "doDockStack2DeployForBft", fmt.Sprintf("Failed to copy necessary files: %v", err)).Json())
		return
	}

	chainFolder := fmt.Sprintf("%s/%s", conf.GetNfsFolder(), chainname)
	//yaml := chainFolder + "/" + chainname + ".yml"
	yaml := chainFolder + "/" + conf.DefaultComposeFileName

	if err := os.WriteFile(yaml, p, 0644); err != nil {
		//ctx.JSON(400, fmt.Sprintf("Failed to write running config file %s: %v", yaml, err))
		ctx.JSON(400, NewErrorMessage(Stack, 5, "doDockStack2DeployForBft", fmt.Sprintf("Failed to write running config file %s: %v", yaml, err)).Json())
		return
	}

	log.Printf(fmt.Sprintf("working directory: %s", chainFolder))
	//out, err := util.DockerComposeDeployUpConfigFile(yaml, chainFolder)
	out, err := util.DockerComposeDeployUp3(chainFolder)
	if err != nil {
		//ctx.JSON(400, fmt.Sprintf("Failed to deploy compose task: %v", err))
		//ctx.JSON(http.StatusInternalServerError, NewErrorMessage(Stack, conf.BrindDownContainersInCompose, "doDockStack2DeployForBft", fmt.Sprintf("Failed to deploy compose task: %s : %v ", out.String(), err)).Json())
		ctx.JSON(http.StatusInternalServerError, NewErrorMessage(Stack, 6, "doDockStack2DeployForBft", fmt.Sprintf("Failed to deploy compose task: %s : %v ", out.String(), err)).Json())
		return
	}

	if err = db.CreateStack(chainname, consensus, string(p), nodeNum); err != nil {
		//ctx.JSON(http.StatusBadRequest, fmt.Sprintf("Failed to store the stack{chainname:%s, consensus:%s, config:%s, nodeNumber:%v}: %v", chainname, consensus, string(p), nodeNum, err))
		ctx.JSON(http.StatusInternalServerError, NewErrorMessage(Stack, 7, "doDockStack2DeployForBft", fmt.Sprintf("Failed to store the stack{chainname:%s, consensus:%s, config:%s, nodeNumber:%v}: %v", chainname, consensus, string(p), nodeNum, err)).Json())
		return
	}

	//ctx.JSON(200, string(out))
	ctx.JSON(200, out.String())
}

func doGenStack2ConfigForBft(ctx *gin.Context) {
	var bft2Config stack.BFT2
	consensus := ctx.GetHeader("Consensus-Template-Type")

	if err := ctx.BindJSON(&bft2Config); err != nil {
		//ctx.JSON(http.StatusBadRequest, err)
		ctx.JSON(http.StatusBadRequest, NewErrorMessage(Stack, 1, "doGenStack2ConfigForBft", fmt.Sprintf("cannot bind form: %v", err)).Json())
		return
	}

	bft2Tmpl := fmt.Sprintf("%s/%s.tmpl", conf.GetStackTemplateFolder(), consensus)
	err := stack.ParseBFT2Template(ctx.Writer, bft2Tmpl, bft2Config)
	if err != nil {
		ctx.JSON(http.StatusBadRequest, fmt.Sprintf("%v", err))
	}
}

//func doDockStack2DeployForPow(ctx *gin.Context) {
//
//	chainname := ctx.PostForm("chainname")
//	nodeNum := ctx.PostForm("nodeNum")
//	consensus := ctx.PostForm("consensus")
//
//	log.Printf("recieved {chainname:%s,nodeNum:%s,consensus:%s}", chainname, nodeNum, consensus)
//
//	if len(chainname) == 0 || len(nodeNum) == 0 {
//		ctx.JSON(400, fmt.Sprintf("Failed to get chainname or nodeNum"))
//		return
//	}
//	if _, err := strconv.Atoi(nodeNum); err != nil {
//		ctx.JSON(400, fmt.Sprintf("%v is not a number", nodeNum))
//		return
//	}
//	nodeNumber, _ := strconv.Atoi(nodeNum)
//	if len(consensus) == 0 {
//		consensus = "poa"
//	}
//
//	file, _ := ctx.FormFile("file")
//	f, err := file.Open()
//	if err != nil {
//		ctx.JSON(400, fmt.Sprintf("Failed to open yaml:%v", err))
//		return
//	}
//	defer f.Close()
//
//	p := make([]byte, file.Size)
//	if _, err := f.Read(p); err != nil {
//		ctx.JSON(400, fmt.Sprintf("Failed to read yaml: %v", err))
//		return
//	}
//
//	if err := stack.CopyDirs_POA2(chainname, consensus); err != nil {
//		ctx.JSON(400, fmt.Sprintf("Failed to copy dirs: %v", err))
//		return
//	}
//
//	if err := stack.BuildPow2NodesDir(nodeNumber, chainname, consensus); err != nil {
//		ctx.JSON(400, fmt.Sprintf("Failed to copy dirs: %v", err))
//		return
//	}
//
//	chainFolder, _ := util.ExpandTilde(fmt.Sprintf("%s/%s", conf.GetNfsFolder(), chainname))
//	yaml := chainFolder + "/" + chainname + ".yml"
//
//	if err := os.WriteFile(yaml, p, 0644); err != nil {
//		ctx.JSON(400, fmt.Sprintf("Failed to write file %s: %v", yaml, err))
//		return
//	}
//
//	log.Printf(fmt.Sprintf("working directory: %s", chainFolder))
//	out, err := util.DockerComposeDeployUpConfigFile(yaml, chainFolder)
//	if err != nil {
//		ctx.JSON(400, fmt.Sprintf("Failed to deploy stack: %v", err))
//		return
//	}
//
//	if err = db.CreateStack(chainname, consensus, string(p), nodeNumber); err != nil {
//		ctx.JSON(http.StatusBadRequest, fmt.Sprintf("Failed to store the stack{chainname:%s, consensus:%s, config:%s, nodeNumber:%v}: %v", chainname, consensus, string(p), nodeNumber, err))
//		return
//	}
//
//	ctx.JSON(200, string(out))
//}

func doDockStack2DeployForPow(ctx *gin.Context) {
	var (
		//hasCompose = false
		hasGenesis = false
	)
	chainname := ctx.PostForm("chainname")
	nodeNum := ctx.PostForm("nodeNum")
	consensus := ctx.PostForm("consensus")

	log.Printf("recieved {chainname:%s,nodeNum:%s,consensus:%s}", chainname, nodeNum, consensus)

	if len(chainname) == 0 || len(nodeNum) == 0 {
		ctx.JSON(400, fmt.Sprintf("Failed to get chainname or nodeNum"))
		return
	}
	if _, err := strconv.Atoi(nodeNum); err != nil {
		ctx.JSON(400, fmt.Sprintf("%v is not a number", nodeNum))
		return
	}
	nodeNumber, _ := strconv.Atoi(nodeNum)
	if len(consensus) == 0 {
		consensus = "poa"
	}

	//file, _ := ctx.FormFile("file")
	//f, err := file.Open()
	//if err != nil {
	//	ctx.JSON(400, fmt.Sprintf("Failed to open yaml:%v", err))
	//	return
	//}
	//defer f.Close()
	//
	//p := make([]byte, file.Size)
	//if _, err := f.Read(p); err != nil {
	//	ctx.JSON(400, fmt.Sprintf("Failed to read yaml: %v", err))
	//	return
	//}
	p, err := readFormFileBytes(ctx, "file")
	if err != nil {
		ctx.JSON(400, fmt.Sprintf("%v", err))
		return
	}
	genesisBytes, err := readFormFileBytes(ctx, "genesis")
	if err != nil {
		log.Printf("warning: an exception was ignored while doing custom genesis: %v", err)
	}
	if len(genesisBytes) > 0 {
		hasGenesis = true
		log.Println("custom genesis size:", len(genesisBytes))
	}

	if err := stack.CopyDirs_POA2(chainname, consensus); err != nil {
		ctx.JSON(400, fmt.Sprintf("Failed to copy dirs: %v", err))
		return
	}

	if err := stack.BuildPow2NodesDir(nodeNumber, chainname, consensus); err != nil {
		ctx.JSON(400, fmt.Sprintf("Failed to copy dirs: %v", err))
		return
	}

	chainFolder, _ := util.ExpandTilde(fmt.Sprintf("%s/%s", conf.GetNfsFolder(), chainname))
	//yaml := chainFolder + "/" + chainname + ".yml"
	yaml := chainFolder + "/" + conf.DefaultComposeFileName
	genesis := chainFolder + "/" + "config/genesis.json"

	if err := os.WriteFile(yaml, p, 0644); err != nil {
		ctx.JSON(400, fmt.Sprintf("Failed to write file %s: %v", yaml, err))
		return
	}

	if hasGenesis {
		if err := os.WriteFile(genesis, genesisBytes, 0644); err != nil {
			ctx.JSON(400, fmt.Sprintf("Failed to write custom genesis file %s: %v", genesis, err))
			return
		}
	}

	log.Printf(fmt.Sprintf("working directory: %s", chainFolder))
	//out, err := util.DockerComposeDeployUpConfigFile(yaml, chainFolder)
	out, err := util.DockerComposeDeployUp3(chainFolder)
	if err != nil {
		//ctx.JSON(400, fmt.Sprintf("Failed to deploy stack: %v", err))
		ctx.JSON(http.StatusInternalServerError, NewErrorMessage(Stack, conf.BrindDownContainersInCompose, "doDockStack2DeployForPow", fmt.Sprintf("Failed to deploy stack: %v", err)).Json())
		return
	}

	if err = db.CreateStack(chainname, consensus, string(p), nodeNumber); err != nil {
		//ctx.JSON(http.StatusBadRequest, fmt.Sprintf("Failed to store the stack{chainname:%s, consensus:%s, config:%s, nodeNumber:%v}: %v", chainname, consensus, string(p), nodeNumber, err))
		ctx.JSON(400, NewErrorMessage(Stack, 2, "doDockStack2DeployForPow", fmt.Sprintf("Failed to store the stack{chainname:%s, consensus:%s, config:%s, nodeNumber:%v}: %v", chainname, consensus, string(p), nodeNumber, err)).Json())
		return
	}

	//ctx.JSON(200, string(out))
	ctx.JSON(200, out.String())
}

func doGenStack2ConfigForPow(ctx *gin.Context) {
	var pow2Config stack.POW2
	consensus := ctx.GetHeader("Consensus-Template-Type")

	if err := ctx.BindJSON(&pow2Config); err != nil {
		ctx.JSON(http.StatusBadRequest, err)
		return
	}
	pow2Config.Nfs, _ = util.ExpandTilde(conf.GetNfsFolder())

	poaTmpl := fmt.Sprintf("%s/%s.tmpl", conf.GetStackTemplateFolder(), consensus)
	expendedPoaTmpl, _ := util.ExpandTilde(poaTmpl)
	err := stack.ParsePOATemplate(ctx.Writer, expendedPoaTmpl, pow2Config)
	if err != nil {
		//ctx.JSON(http.StatusBadRequest, fmt.Sprintf("%v", err))
		ctx.JSON(http.StatusBadRequest, NewErrorMessage(Stack, 1, "doGenStack2ConfigForPow", fmt.Sprintf("%v", err)).Json())
	}
}

//func doDockStack2DeployForEth(ctx *gin.Context) {
//
//	chainname := ctx.PostForm("chainname")
//	nodeNum := ctx.PostForm("nodeNum")
//	consensus := ctx.PostForm("consensus")
//
//	log.Printf("recieved {chainname:%s,nodeNum:%s,consensus:%s}", chainname, nodeNum, consensus)
//
//	if len(chainname) == 0 || len(nodeNum) == 0 {
//		ctx.JSON(400, fmt.Sprintf("Failed to get chainname or nodeNum"))
//		return
//	}
//	if _, err := strconv.Atoi(nodeNum); err != nil {
//		ctx.JSON(400, fmt.Sprintf("%v is not a number", nodeNum))
//		return
//	}
//	nodeNumber, _ := strconv.Atoi(nodeNum)
//	if len(consensus) == 0 {
//		consensus = "poa"
//	}
//
//	file, _ := ctx.FormFile("file")
//	f, err := file.Open()
//	if err != nil {
//		ctx.JSON(400, fmt.Sprintf("Failed to open yaml:%v", err))
//		return
//	}
//	defer f.Close()
//
//	p := make([]byte, file.Size)
//	if _, err := f.Read(p); err != nil {
//		ctx.JSON(400, fmt.Sprintf("Failed to read yaml: %v", err))
//		return
//	}
//
//	if err := stack.CopyDirs_POA2(chainname, consensus); err != nil {
//		ctx.JSON(400, fmt.Sprintf("Failed to copy dirs: %v", err))
//		return
//	}
//
//	if err := stack.BuildPoa2NodesDir(nodeNumber, chainname, consensus); err != nil {
//		ctx.JSON(400, fmt.Sprintf("Failed to copy dirs: %v", err))
//		return
//	}
//
//	chainFolder, _ := util.ExpandTilde(fmt.Sprintf("%s/%s", conf.GetNfsFolder(), chainname))
//	yaml := chainFolder + "/" + chainname + ".yml"
//
//	if err := os.WriteFile(yaml, p, 0644); err != nil {
//		ctx.JSON(400, fmt.Sprintf("Failed to write file %s: %v", yaml, err))
//		return
//	}
//
//	log.Printf(fmt.Sprintf("working directory: %s", chainFolder))
//	out, err := util.DockerComposeDeployUpConfigFile(yaml, chainFolder)
//	if err != nil {
//		ctx.JSON(400, fmt.Sprintf("Failed to deploy stack: %v", err))
//		return
//	}
//
//	if err = db.CreateStack(chainname, consensus, string(p), nodeNumber); err != nil {
//		ctx.JSON(http.StatusBadRequest, fmt.Sprintf("Failed to store the stack{chainname:%s, consensus:%s, config:%s, nodeNumber:%v}: %v", chainname, consensus, string(p), nodeNumber, err))
//		return
//	}
//
//	ctx.JSON(200, string(out))
//}

// 2024/10/29  optioanlly, allowing users to post custom genesis.json (keynmae is 'genesis')
func doDockStack2DeployForEth(ctx *gin.Context) {
	var (
		//hasCompose = false
		hasGenesis = false
	)
	chainname := ctx.PostForm("chainname")
	nodeNum := ctx.PostForm("nodeNum")
	consensus := ctx.PostForm("consensus")

	log.Printf("recieved {chainname:%s,nodeNum:%s,consensus:%s}", chainname, nodeNum, consensus)

	if len(chainname) == 0 || len(nodeNum) == 0 {
		ctx.JSON(400, fmt.Sprintf("Failed to get chainname or nodeNum"))
		return
	}
	if _, err := strconv.Atoi(nodeNum); err != nil {
		ctx.JSON(400, fmt.Sprintf("%v is not a number", nodeNum))
		return
	}
	nodeNumber, _ := strconv.Atoi(nodeNum)
	if len(consensus) == 0 {
		consensus = "poa"
	}

	//file, _ := ctx.FormFile("file")
	//f, err := file.Open()
	//if err != nil {
	//	ctx.JSON(400, fmt.Sprintf("Failed to open yaml:%v", err))
	//	return
	//}
	//defer f.Close()
	//
	//p := make([]byte, file.Size)
	//if _, err := f.Read(p); err != nil {
	//	ctx.JSON(400, fmt.Sprintf("Failed to read yaml: %v", err))
	//	return
	//}
	p, err := readFormFileBytes(ctx, "file")
	if err != nil {
		ctx.JSON(400, fmt.Sprintf("%v", err))
		return
	}

	genesisBytes, err := readFormFileBytes(ctx, "genesis")
	if err != nil {
		log.Printf("warning: an exception was ignored while doing custom genesis: %v", err)
	}
	log.Println("genesis reading is over: ", len(genesisBytes), " bytes")
	if len(genesisBytes) > 0 {
		hasGenesis = true
		log.Println("custom genesis size:", len(genesisBytes))
	}

	if err := stack.CopyDirs_POA2(chainname, consensus); err != nil {
		ctx.JSON(400, fmt.Sprintf("Failed to copy dirs: %v", err))
		return
	}

	if err := stack.BuildPoa2NodesDir(nodeNumber, chainname, consensus); err != nil {
		ctx.JSON(400, fmt.Sprintf("Failed to copy dirs: %v", err))
		return
	}

	chainFolder, _ := util.ExpandTilde(fmt.Sprintf("%s/%s", conf.GetNfsFolder(), chainname))
	//yaml := chainFolder + "/" + chainname + ".yml"
	yaml := chainFolder + "/" + conf.DefaultComposeFileName
	genesis := chainFolder + "/" + "config/genesis.json"

	if err := os.WriteFile(yaml, p, 0644); err != nil {
		ctx.JSON(400, fmt.Sprintf("Failed to write file %s: %v", yaml, err))
		return
	}

	if hasGenesis {
		log.Printf("start to write genesis file: path[%s],size[%d]", genesis, len(genesisBytes))
		if err := os.WriteFile(genesis, genesisBytes, 0644); err != nil {
			ctx.JSON(400, fmt.Sprintf("Failed to write custom genesis file %s: %v", genesis, err))
			return
		}
	}

	log.Printf(fmt.Sprintf("working directory: %s", chainFolder))
	//out, err := util.DockerComposeDeployUpConfigFile(yaml, chainFolder)
	out, err := util.DockerComposeDeployUp3(chainFolder)
	if err != nil {
		//ctx.JSON(400, fmt.Sprintf("Failed to deploy stack: %v", err))
		ctx.JSON(http.StatusInternalServerError, NewErrorMessage(Stack, conf.BrindDownContainersInCompose, "doDockStack2DeployForEth", fmt.Sprintf("Failed to deploy stack: %v %s", err, out.String())).Json())
		return
	}

	if err = db.CreateStack(chainname, consensus, string(p), nodeNumber); err != nil {
		//ctx.JSON(http.StatusBadRequest, fmt.Sprintf("Failed to store the stack{chainname:%s, consensus:%s, config:%s, nodeNumber:%v}: %v", chainname, consensus, string(p), nodeNumber, err))
		ctx.JSON(http.StatusBadRequest, NewErrorMessage(Stack, 1, "doDockStack2DeployForEth", fmt.Sprintf("Failed to store the stack{chainname:%s, consensus:%s, config:%s, nodeNumber:%v}: %v", chainname, consensus, string(p), nodeNumber, err)).Json())
		return
	}

	//ctx.JSON(http.StatusOK, string(out))
	ctx.JSON(http.StatusOK, out.String())
}

func doGenStack2Config(ctx *gin.Context) {
	var poaConfig stack.POA
	consensus := ctx.GetHeader("Consensus-Template-Type")

	if err := ctx.BindJSON(&poaConfig); err != nil {
		//ctx.JSON(http.StatusBadRequest, gin.H{"error": err.Error()})
		ctx.JSON(http.StatusBadRequest, err)
		return
	}
	poaConfig.Nfs, _ = util.ExpandTilde(conf.GetNfsFolder())

	//poaTmpl := "/workspace/chen/blockchains-aio/stack/poa.tmpl"
	poaTmpl := fmt.Sprintf("%s/%s.tmpl", conf.GetStackTemplateFolder(), consensus)
	expendedPoaTmpl, _ := util.ExpandTilde(poaTmpl)
	err := stack.ParsePOATemplate(ctx.Writer, expendedPoaTmpl, poaConfig)
	if err != nil {
		ctx.JSON(http.StatusBadRequest, fmt.Sprintf("%v", err))
	}
}

func readFormFileBytes(ctx *gin.Context, fileName string) ([]byte, error) {
	file, err := ctx.FormFile(fileName)
	if err != nil {
		return nil, errors.Unwrap(fmt.Errorf("failed to extract form file %s: %v", fileName, err))
	}
	f, err := file.Open()
	if err != nil {
		return nil, errors.Unwrap(fmt.Errorf("Failed to open file %s: %v", fileName, err))
	}
	defer f.Close()

	p := make([]byte, file.Size)
	if _, err := f.Read(p); err != nil {
		return nil, errors.Unwrap(fmt.Errorf("Failed to read file %s: %v", fileName, err))
	}

	return p, nil
}
